{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Comparison with Pandas API"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This page recreates the [pandas chart visualization guide](https://pandas.pydata.org/pandas-docs/stable/user_guide/visualization.html). It shows how to configure and control the output of `pd.DataFrame.plot` and `pd.Series.plot` to generate hvPlot plots. For comparison, the page includes both the Pandas & Matplotlib and Pandas & hvPlot plot versions.\n",
    "\n",
    ":::{note}\n",
    "You will find many notes in this page that are used to highlight differences between Pandas and hvPlot.\n",
    ":::\n",
    "\n",
    ":::{tip}\n",
    "We generally recommend using hvPlot by installing the `hvplot` namespace on Pandas objects with `import hvplot.pandas`.\n",
    ":::"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import pandas as pd\n",
    "\n",
    "pd.options.plotting.backend = 'hvplot'"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Basic Plotting: `plot`"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The `plot` method on Series and DataFrame is just a simple wrapper around `hvPlot()`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": [
     "remove-cell"
    ]
   },
   "outputs": [],
   "source": [
    "%matplotlib inline\n",
    "np.random.seed(123456)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "ts = pd.Series(np.random.randn(1000), index=pd.date_range('1/1/2000', periods=1000))\n",
    "ts = ts.cumsum()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "ts.plot(backend='matplotlib');"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "ts.plot()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "If the index consists of dates, they will be formatted nicely along the x-axis as per above.\n",
    "\n",
    ":::{note}\n",
    "Dates will be sorted as long as `sort_date=True`.\n",
    ":::\n",
    "\n",
    "On DataFrame, `plot()` is a convenience to plot all of the columns with labels:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df = pd.DataFrame(np.random.randn(1000, 4), index=ts.index, columns=list('ABCD'))\n",
    "df = df.cumsum()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df.plot(backend='matplotlib');"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df.plot()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    ":::{note}\n",
    "The default colors used in hvPlot are different from pandas but you can pass your preferred colors as a list via the `color` keyword.\n",
    ":::"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df.plot(color=['#1F77B4', '#FF7F0D', '#B5DDB5', '#D93A3B'])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "You can plot one column versus another using the *x* and *y* keywords in `plot()`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df3 = pd.DataFrame(np.random.randn(1000, 2), columns=['B', 'C']).cumsum()\n",
    "df3['A'] = pd.Series(list(range(len(df))))\n",
    "df3.plot(x='A', y='B', backend='matplotlib');"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df3.plot(x='A', y='B')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Other Plots\n",
    "\n",
    "Plotting methods allow for a handful of plot styles other than the default `line` plot. These methods can be provided as the `kind` keyword argument to `plot()`. These include:\n",
    "\n",
    "* `bar` or `barh` for bar plots\n",
    "* `hist` for histogram\n",
    "* `box` for boxplot\n",
    "* `kde` or `density` for density plots\n",
    "* `area` for area plots\n",
    "* `scatter` for scatter plots\n",
    "* `hexbin` for hexagonal bin plots\n",
    "* [NOT IMPLEMENTED]: `‘pie’` for pie plots\n",
    "\n",
    "For example, a bar plot can be created the following way:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df.iloc[5].plot(kind='bar', backend='matplotlib');"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df.iloc[5].plot(kind='bar')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "You can also create these other plots using the methods `DataFrame.plot.<kind>` instead of providing the `kind` keyword argument. This makes it easier to discover plot methods and the specific arguments they use."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In addition to these `kind`s, there are the `DataFrame.hist()`, and `DataFrame.boxplot()` methods, which use a separate interface.\n",
    "\n",
    "Finally, there are several plotting functions in `hvplot.plotting` that take a `Series` or `DataFrame` as an argument. These include:\n",
    "\n",
    "* `Scatter Matrix`\n",
    "* `Andrews Curves`\n",
    "* `Parallel Coordinates`\n",
    "* `Lag Plot`"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    ":::{note}\n",
    "The following plots are not currently supported in hvPlot:\n",
    "* `Autocorrelation Plot`\n",
    "* `Bootstrap Plot`\n",
    "* `RadViz`\n",
    ":::\n",
    "\n",
    "Plots may also be adorned with errorbars or tables."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Bar plots\n",
    "\n",
    "For labeled, non-time series data, you may wish to produce a bar plot:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df.iloc[5].plot.bar(backend='matplotlib');"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df.iloc[5].plot.bar()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Calling a DataFrame’s `plot.bar()` method produces a multiple bar plot:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df2 = pd.DataFrame(np.random.rand(10, 4), columns=['a', 'b', 'c', 'd'])\n",
    "df2.plot.bar(backend='matplotlib');"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df2.plot.bar()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "To produce a stacked bar plot, pass `stacked=True`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df2.plot.bar(stacked=True, backend='matplotlib');"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df2.plot.bar(stacked=True)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "To get horizontal bar plots, use the `barh` method:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df2.plot.barh(stacked=True, backend='matplotlib');"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df2.plot.barh(stacked=True)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Histograms"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Histogram can be drawn by using the `DataFrame.plot.hist()` and `Series.plot.hist()` methods."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df4 = pd.DataFrame({'a': np.random.randn(1000) + 1, 'b': np.random.randn(1000),\n",
    "                    'c': np.random.randn(1000) - 1}, columns=['a', 'b', 'c'])\n",
    "\n",
    "df4.plot.hist(alpha=0.5, backend='matplotlib');"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df4.plot.hist(alpha=0.5)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    ":::{note}\n",
    "Stacking for histograms is not yet supported in hvPlot\n",
    "\n",
    "```python\n",
    "# df4.plot.hist(stacked=True, bins=20);\n",
    "```\n",
    ":::"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "You can pass other keywords. For example, horizontal and cumulative histogram can be drawn by `invert=True` and `cumulative=True`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df4['a'].plot.hist(orientation='horizontal', cumulative=True, backend='matplotlib');"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    ":::{note}\n",
    "hvPlot uses `invert=True` instead of `orientation='horizontal'`.\n",
    ":::"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df4['a'].plot.hist(invert=True, cumulative=True)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The existing interface `DataFrame.hist` to plot histogram still can be used."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df['A'].diff().hist(backend='matplotlib');"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df['A'].diff().hist()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    ":::{note}\n",
    "Pandas' `DataFrame.hist()` plots the histograms of the columns on multiple subplots. hvPlot creates instead an overlay of histogram plots. To reproduce Pandas' behavior, you can set `subplots=True` to create a layout of plots (1 per column in this case), and additionally call `.cols(2)` on the object returned to lay the plots in a layout with a maximum number of 2 columns.\n",
    ":::"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df.diff().hist(color='k', alpha=0.5, bins=50, backend='matplotlib');"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df.diff().hist(color='k', alpha=0.5, bins=50, subplots=True, width=300).cols(2)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The `by` keyword can be specified to plot grouped histograms:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "data = pd.Series(np.random.randn(1000))\n",
    "df5 = pd.DataFrame({'data': data, 'by_column': np.random.randint(0, 4, 1000)})\n",
    "\n",
    "data.hist(by=np.random.randint(0, 4, 1000), figsize=(6, 4), backend='matplotlib');"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    ":::{note}\n",
    "Pandas' `hist` method accepts a Numpy NdArray for `by` but hvPlot does not, which is why in the example above an extra column `'by_column'` was created.\n",
    ":::"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df5.hist(by='by_column', width=300, subplots=True).cols(2)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In Pandas, the `by` keyword can also be specified in `DataFrame.plot.hist()`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "data = pd.DataFrame(\n",
    "    {\n",
    "        \"a\": np.random.choice([\"x\", \"y\", \"z\"], 1000),\n",
    "        \"b\": np.random.choice([\"e\", \"f\", \"g\"], 1000),\n",
    "        \"c\": np.random.randn(1000),\n",
    "        \"d\": np.random.randn(1000) - 1,\n",
    "    },\n",
    ")\n",
    "data.plot.hist(by=[\"a\", \"b\"], figsize=(10, 5), backend='matplotlib');"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    ":::{note}\n",
    "This is not yet supported in hvPlot but a similar plot can be generated using HoloViews directly.\n",
    ":::"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import holoviews as hv\n",
    "from holoviews.operation import histogram\n",
    "\n",
    "ds = hv.Dataset(data, kdims=['a', 'b'], vdims=['c', 'd'])\n",
    "dsg = ds.groupby(['a', 'b'])\n",
    "(\n",
    "    hv.NdLayout(histogram(dsg, dimension='c'))\n",
    "    * hv.NdLayout(histogram(dsg, dimension='d'))\n",
    ").opts(\n",
    "    hv.opts.Histogram(height=100, width=800)\n",
    ").cols(1)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Box Plots\n",
    "\n",
    "Boxplot can be drawn by calling `Series.plot.box()` and `DataFrame.plot.box()`, or `DataFrame.boxplot()` to visualize the distribution of values within each column.\n",
    "\n",
    "For instance, here is a boxplot representing five trials of 10 observations of a uniform random variable on [0,1).\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df = pd.DataFrame(np.random.rand(10, 5), columns=['A', 'B', 'C', 'D', 'E'])\n",
    "df.plot.box(backend='matplotlib');"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df.plot.box()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Using the matplotlib backend, boxplot can be colorized by passing `color` keyword. You can pass a `dict` whose keys are `boxes`, `whiskers`, `medians` and `caps`. If some keys are missing in the `dict`, default colors are used for the corresponding artists. Also, boxplot has `sym` keyword to specify fliers style."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "color = {\n",
    "    \"boxes\": \"DarkGreen\",\n",
    "    \"whiskers\": \"DarkOrange\",\n",
    "    \"medians\": \"DarkBlue\",\n",
    "    \"caps\": \"Gray\",\n",
    "}\n",
    "\n",
    "df.plot.box(color=color, sym='r+', backend='matplotlib');"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    ":::{note}\n",
    "Setting `color` this way or `sym` isn't supported in hvPlot. However hvPlot supports passing down backend-specific style options that allow some level of customization.\n",
    ":::"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df.plot.box(\n",
    "    box_fill_color='white',\n",
    "    box_line_color='DarkGreen',\n",
    "    whisker_line_color='DarkOrange',\n",
    "    outlier_color='red',\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In Pandas, you can pass other keywords supported by matplotlib `boxplot`. For example, horizontal and custom-positioned boxplot can be drawn by `vert=False` and `positions` keywords."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df.plot.box(vert=False, positions=[1, 4, 5, 6, 8], backend='matplotlib');"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    ":::{note}\n",
    "`vert` and `positions` are not supported by hvPlot and the Bokeh extension. Instead, and for example, horizontal boxplots can be drawn by `invert=True`.\n",
    ":::"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df.plot.box(invert=True)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The existing interface `DataFrame.boxplot` to plot boxplot still can be used."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df = pd.DataFrame(np.random.rand(10, 5))\n",
    "df.boxplot(backend='matplotlib')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df.boxplot()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In Pandas, you can create a stratified boxplot using the `by` keyword argument to create groupings."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df = pd.DataFrame(np.random.rand(10,2), columns=['Col1', 'Col2'])\n",
    "df['X'] = pd.Series(['A','A','A','A','A','B','B','B','B','B'])\n",
    "df.boxplot(by='X', backend='matplotlib');"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    ":::{note}\n",
    "In hvPlot this plot can be generated differently by transforming first the data to long format with Pandas and then using `by` and `col` to facet the plot.\n",
    ":::"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df_melted = df.melt(id_vars=['X'], value_vars=['Col1', 'Col2'], var_name='Col')\n",
    "df_melted.boxplot(col='Col', by='X')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "You can also pass a subset of columns to plot, as well as group by multiple columns:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df = pd.DataFrame(np.random.rand(10,3), columns=['Col1', 'Col2', 'Col3'])\n",
    "\n",
    "df['X'] = pd.Series(['A','A','A','A','A','B','B','B','B','B'])\n",
    "df['Y'] = pd.Series(['A','B','A','B','A','B','A','B','A','B'])\n",
    "\n",
    "bp = df.boxplot(column=[\"Col1\", \"Col2\"], by=[\"X\", \"Y\"], backend='matplotlib')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    ":::{note}\n",
    "In hvPlot this plot can be generated differently by transforming first the data to long format with Pandas and then using `by` and `col` to facet the plot.\n",
    ":::"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df_melted = df.melt(id_vars=['X', 'Y'], value_vars=['Col1', 'Col2'], var_name='Col')\n",
    "df_melted.boxplot(col='Col', by=['X', 'Y'])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df_box = pd.DataFrame(np.random.randn(50, 2))\n",
    "df_box['g'] = np.random.choice(['A', 'B'], size=50)\n",
    "df_box.loc[df_box['g'] == 'B', 1] += 3\n",
    "df_box.boxplot(row='g')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "For more control over the ordering of the levels, we can perform a groupby on the data before plotting."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df_box.groupby('g').boxplot()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Area Plot\n",
    "\n",
    "You can create area plots with `Series.plot.area()` and `DataFrame.plot.area()`. Area plots are stacked by default. To produce stacked area plot, each column must be either all positive or all negative values.\n",
    "\n",
    "When input data contains `NaN`, it will be automatically filled by 0. If you want to drop or fill by different values, use `dataframe.dropna()` or `dataframe.fillna()` before calling `plot`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df = pd.DataFrame(np.random.rand(10, 4), columns=['a', 'b', 'c', 'd'])\n",
    "df.plot.area(backend='matplotlib');"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df.plot.area()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "To produce an unstacked plot, pass `stacked=False`. In Pandas, alpha value is set to 0.5 unless otherwise specified:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df.plot.area(stacked=False, backend='matplotlib');"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df.plot.area(stacked=False, alpha=0.5)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Scatter Plot"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Scatter plots can be drawn by using the `DataFrame.plot.scatter()` method. Scatter plots require that x and y be specified using the `x` and `y` keywords."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df = pd.DataFrame(np.random.rand(50, 4), columns=['a', 'b', 'c', 'd'])\n",
    "df[\"species\"] = pd.Categorical(\n",
    "\n",
    "    [\"setosa\"] * 20 + [\"versicolor\"] * 20 + [\"virginica\"] * 10\n",
    "\n",
    ")\n",
    "df.plot.scatter(x='a', y='b', backend='matplotlib');"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df.plot.scatter(x='a', y='b')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In Pandas, to plot multiple column groups in a single axes, repeat `plot` method specifying target `ax`. It is recommended to specify color and label keywords to distinguish each groups.\n",
    "\n",
    ":::{note}\n",
    "In hvPlot, to plot multiple column groups in a single axes, repeat `plot` method and then use the overlay operator (`*`) to combine the plots.\n",
    ":::"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "ax = df.plot.scatter(x=\"a\", y=\"b\", color=\"DarkBlue\", label=\"Group 1\", backend='matplotlib')\n",
    "df.plot.scatter(x=\"c\", y=\"d\", color=\"DarkGreen\", label=\"Group 2\", ax=ax, backend='matplotlib');"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "plot_1 = df.plot.scatter(x='a', y='b', color='DarkBlue', label='Group 1')\n",
    "plot_2 = df.plot.scatter(x='c', y='d', color='DarkGreen', label='Group 2')\n",
    "plot_1 * plot_2"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The keyword `c` may be given as the name of a column to provide colors for each point:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df.plot.scatter(x='a', y='b', c='c', s=50, backend='matplotlib');"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df.plot.scatter(x='a', y='b', c='c', s=50)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    ":::{note}\n",
    "The default colors for continuous labelling in hvPlot is the `kbc_r` cmap. You can change the colormap to the pandas default by using the `cmap` keyword.\n",
    ":::"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df.plot.scatter(x='a', y='b', c='c', s=50, cmap='viridis')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "If a categorical column is passed to `c`, then, in Pandas, a discrete colorbar will be produced:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df.plot.scatter(x=\"a\", y=\"b\", c=\"species\", s=50, cmap=\"viridis\", backend='matplotlib');"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    ":::{note}\n",
    "In hvPlot, setting `c` does not display a colorbar but a legend. The default categorical colormap used is [*glasbey_category10*](https://colorcet.holoviz.org/user_guide/Categorical.html#starting-colors).\n",
    ":::"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df.plot.scatter(x=\"a\", y=\"b\", c=\"species\", s=50)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "You can pass other keywords supported by matplotlib `scatter`. The example below shows a bubble chart using a column of the `DataFrame` as the bubble size."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df.plot.scatter(x=\"a\", y=\"b\", s=df[\"c\"] * 200, backend='matplotlib');"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    ":::{note}\n",
    "In hvPlot you may need to increase `s` to obtain markers with a size similar to the ones generated with Pandas and Matplotlib.\n",
    ":::"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df.plot.scatter(x='a', y='b', s=df['c'] * 500)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    ":::{note}\n",
    "In hvPlot, the same effect can be accomplished using the `scale` option.\n",
    ":::"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df.plot.scatter(x='a', y='b', s='c', scale=25)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Hexagonal Bin Plot\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "You can create hexagonal bin plots with `DataFrame.plot.hexbin()`. Hexbin plots can be a useful alternative to scatter plots if your data are too dense to plot each point individually."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df = pd.DataFrame(np.random.randn(1000, 2), columns=['a', 'b'])\n",
    "df['b'] = df['b'] + np.arange(1000)\n",
    "df.plot.hexbin(x=\"a\", y=\"b\", gridsize=25, backend='matplotlib');"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    ":::{note}\n",
    "We pass `aspect=1` to hvPlot to generate a plot with dimensions similar to the one generated with Pandas and Matplotlib\n",
    ":::"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df.plot.hexbin(x='a', y='b', gridsize=25, aspect=1)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "A useful keyword argument is `gridsize`; it controls the number of hexagons in the x-direction, and defaults to 100. A larger `gridsize` means more, smaller bins.\n",
    "\n",
    "By default, a histogram of the counts around each `(x, y)` point is computed. You can specify alternative aggregations by passing values to the `C` and `reduce_C_function` arguments. `C` specifies the value at each `(x, y)` point and `reduce_C_function` is a function of one argument that reduces all the values in a bin to a single number (e.g. `mean`, `max`, `sum`, `std`). In this example the positions are given by columns `a` and `b`, while the value is given by column `z`. The bins are aggregated with Numpy’s `max` function."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df = pd.DataFrame(np.random.randn(1000, 2), columns=['a', 'b'])\n",
    "df['b'] = df['b'] + np.arange(1000)\n",
    "df['z'] = np.random.uniform(0, 3, 1000)\n",
    "df.plot.hexbin(x='a', y='b', C='z', reduce_C_function=np.max, gridsize=25, backend='matplotlib');"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    ":::{note}\n",
    "In hvPlot, `reduce_function` is used instead of `reduce_C_function`.\n",
    ":::"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df.plot.hexbin(x='a', y='b', C='z', reduce_function=np.max, gridsize=25, aspect=1)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Pie plot\n",
    "\n",
    ":::{note}\n",
    "Not yet implemented in hvPlot/HoloViews.\n",
    ":::"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Density Plot\n",
    "\n",
    "You can create density plots using the `Series.plot.kde()` and `DataFrame.plot.kde()` methods."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "ser = pd.Series(np.random.randn(1000))\n",
    "ser.plot.kde(backend='matplotlib');"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "ser.plot.kde()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Plotting with missing data\n",
    "\n",
    "hvPlot tries to be pragmatic about plotting `DataFrames` or `Series` that contain missing data. Missing values are dropped, left out, or filled depending on the plot type.\n",
    "\n",
    "*(To be confirmed)*\n",
    "\n",
    "| Plot Type | Nan Handling |\n",
    "| --- | --- |\n",
    "| Line | Leave gaps at NaNs |\n",
    "| Line (stacked) | Fill 0's\n",
    "| Bar | Fill 0's |\n",
    "| Scatter | Drop NaNs\n",
    "| Histogram | Drop NaNs (column-wise)\n",
    "| Box | Drop NaNs (column-wise)\n",
    "| Area |Fill 0's\n",
    "| KDE | Drop NaNs (column-wise)\n",
    "| Hexbin | Fill 0's\n",
    "| Pie | *Not supported*"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Plotting Tools\n",
    "\n",
    "These functions can be imported from `hvplot.plotting` and take a `Series` or `DataFrame` as an argument.\n",
    "\n",
    "### Scatter Matrix Plot\n",
    "\n",
    "You can create a scatter plot matrix using the `scatter_matrix` function:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from pandas.plotting import scatter_matrix as pd_scatter_matrix\n",
    "\n",
    "df = pd.DataFrame(np.random.randn(1000, 4), columns=['a', 'b', 'c', 'd'])\n",
    "pd_scatter_matrix(df, alpha=0.2, figsize=(6, 6), diagonal='kde');"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from hvplot import scatter_matrix\n",
    "\n",
    "scatter_matrix(df, alpha=0.2, diagonal='kde')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    ":::{note}\n",
    "In hvPlot, since scatter matrix plots may generate a large number of points (the one above renders 120,000 points!), you may want to take advantage of the power provided by [Datashader](https://datashader.org/) to rasterize the off-diagonal plots into a fixed-resolution representation.\n",
    "\n",
    "```python\n",
    "df = pd.DataFrame(np.random.randn(10000, 4), columns=['a', 'b', 'c', 'd'])\n",
    "\n",
    "scatter_matrix(df, rasterize=True, dynspread=True)\n",
    "```\n",
    ":::"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Andrews Curves"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Andrews curves allow one to plot multivariate data as a large number of curves that are created using the attributes of samples as coefficients for Fourier series, see the [Wikipedia entry](https://en.wikipedia.org/wiki/Andrews_plot) for more information. By coloring these curves differently for each class it is possible to visualize data clustering. Curves belonging to samples of the same class will usually be closer together and form larger structures."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import hvsampledata\n",
    "\n",
    "dfp = hvsampledata.penguins(\"pandas\")\n",
    "df_data = dfp[['bill_length_mm', 'bill_depth_mm', 'flipper_length_mm', 'body_mass_g']].sample(frac=0.3)\n",
    "df_data = (df_data - df_data.min()) / (df_data.max() - df_data.min())\n",
    "df_data['species'] = dfp['species']"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from pandas.plotting import andrews_curves\n",
    "\n",
    "andrews_curves(df_data, 'species');"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from hvplot.plotting import andrews_curves\n",
    "\n",
    "andrews_curves(df_data, 'species')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Parallel Coordinates"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Parallel coordinates is a plotting technique for plotting multivariate data, see the [Wikipedia entry](https://en.wikipedia.org/wiki/Parallel_coordinates) for an introduction. Parallel coordinates allows one to see clusters in data and to estimate other statistics visually. Using parallel coordinates points are represented as connected line segments. Each vertical line represents one attribute. One set of connected line segments represents one data point. Points that tend to cluster will appear closer together."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from pandas.plotting import parallel_coordinates\n",
    "\n",
    "parallel_coordinates(df_data, 'species');"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from hvplot.plotting import parallel_coordinates\n",
    "\n",
    "parallel_coordinates(df_data, 'species')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Lag Plot \n",
    "\n",
    "Lag plots are used to check if a data set or time series is random. Random data should not exhibit any structure in the lag plot. Non-random structure implies that the underlying data are not random. The `lag` argument may be passed, and when `lag=1` the plot is essentially `data[:-1]` vs. `data[1:]`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from pandas.plotting import lag_plot\n",
    "\n",
    "spacing = np.linspace(-99 * np.pi, 99 * np.pi, num=1000)\n",
    "data = pd.Series(0.1 * np.random.rand(1000) + 0.9 * np.sin(spacing))\n",
    "\n",
    "lag_plot(data);"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from hvplot.plotting import lag_plot\n",
    "\n",
    "lag_plot(data)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Autocorrelation plot\n",
    "\n",
    ":::{note}\n",
    "Not yet implemented in hvPlot.\n",
    ":::"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Bootstrap plot\n",
    "\n",
    ":::{note}\n",
    "Not yet implemented in hvPlot.\n",
    ":::"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### RadViz\n",
    "\n",
    ":::{note}\n",
    "Not yet implemented in hvPlot.\n",
    ":::"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Plot Formatting\n",
    "\n",
    "### Setting the plot style\n",
    "\n",
    "TBD\n",
    "\n",
    "### General plot style arguments\n",
    "\n",
    "Most plotting methods have a set of keyword arguments that control the layout and formatting of the returned plot:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "ts.plot(style='k--', label='Series', backend='matplotlib');"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    ":::{note}\n",
    "The `style` keyword to customize line plots isn't supported in hvPlot. Instead, hvPlot offers some generic styling options like `c` (alias for `color`), and backend-specific styling options like `line_dash`.\n",
    ":::"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "ts.plot(c='k', line_dash='dashed', label='Series')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "For each kind of plot (e.g. `line`, `bar`, `scatter`) any additional arguments keywords are passed along to the corresponding HoloViews object (`hv.Curve`, `hv.Bar`, `hv.Scatter`). These can be used to control additional styling, beyond what hvPlot provides.\n",
    "\n",
    "### Controlling the legend\n",
    "\n",
    "You may set the `legend` argument to `False` to hide the legend, which is shown by default."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df = pd.DataFrame(np.random.randn(1000, 4), index=ts.index, columns=list(\"ABCD\"))\n",
    "df = df.cumsum()\n",
    "\n",
    "df.plot(legend=False, backend='matplotlib');"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df.plot(legend=False)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    ":::{note}\n",
    "In hvPlot, you can also control the placement of the legend using the same `legend` argument set to one of: `'top_right'`, `'top_left'`, `'bottom_left'`, `'bottom_right'`, `'right'`, `'left'`, `'top'`, or `'bottom'`.\n",
    ":::"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df.plot(legend='top_left')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Controlling the labels\n",
    "\n",
    "You may set the `xlabel` and `ylabel` arguments to give the plot custom labels for x and y axis. By default, pandas will pick up index name as xlabel, while leaving it empty for ylabel."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df.plot(xlabel=\"new x\", ylabel=\"new y\", backend='matplotlib');"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df.plot(xlabel=\"new x\", ylabel=\"new y\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Scales\n",
    "\n",
    "You may pass `logy` to get a log-scale Y axis."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "ts = pd.Series(np.random.randn(1000), index=pd.date_range(\"1/1/2000\", periods=1000))\n",
    "ts = np.exp(ts.cumsum())\n",
    "ts.plot(logy=True, backend='matplotlib');"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "ts.plot(logy=True)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "See also the `logx` and `loglog` keyword arguments."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    ":::{note}\n",
    "In hvPlot, `logz=True` allows to get a log-scale on the color axis.\n",
    ":::"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "dfh = pd.DataFrame(np.random.randn(1000, 2), columns=['a', 'b'])\n",
    "dfh['b'] = dfh['b'] + np.arange(1000)\n",
    "dfh.plot.hexbin(x='a', y='b', gridsize=25, width=500, height=400, logz=True)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Plotting on a Secondary Y-axis\n",
    "\n",
    "In Pandas, to plot data on a secondary y-axis, use the `secondary_y` keyword:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df[\"A\"].plot(backend='matplotlib')\n",
    "df[\"B\"].plot(secondary_y=True, style=\"g\", backend='matplotlib');"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    ":::{note}\n",
    "hvPlot does not currently have built-in support for multiple y-axes, but you can achieve this effect by overlaying multiple plots and setting the `multi_y` option in `.opts()`. This allows each y-variable to be displayed on a separate axis.\n",
    ":::"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "(df.plot(y='A') * df.plot(y='B') * df.plot(y='C')).opts(multi_y=True)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Suppressing tick resolution adjustment\n",
    "pandas includes automatic tick resolution adjustment for regular frequency time-series data. For limited cases where pandas cannot infer the frequency information (e.g., in an externally created `twinx`), you can choose to suppress this behavior for alignment purposes.\n",
    "\n",
    "Here is the default behavior, notice how the x-axis tick labeling is performed:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df['A'].plot(backend='matplotlib');"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df['A'].plot()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Using the `x_compat` parameter, you can suppress this behavior:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df[\"A\"].plot(x_compat=True, backend='matplotlib');"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    ":::{note}\n",
    "In hvPlot, formatters can be set using format strings, by declaring bokeh `TickFormatters`, or using custom functions. See [HoloViews Tick Docs](https://holoviews.org/user_guide/Customizing_Plots.html#axis-ticks) for more information.\n",
    ":::"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from bokeh.models.formatters import DatetimeTickFormatter\n",
    "\n",
    "formatter = DatetimeTickFormatter(months='%b %Y')\n",
    "df['A'].plot(yformatter='$%.2f', xformatter=formatter)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Subplots\n",
    "\n",
    "Each `Series` in a `DataFrame` can be plotted on a different axis with the `subplots` keyword."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df.plot(subplots=True, figsize=(6, 6), backend='matplotlib');"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    ":::{note}\n",
    "In hvPlot, you can control the maximum number `N` of plots displayed on a row by calling `.cols(N)` on the HoloViews layout returned by the `.plot()` call.\n",
    ":::"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df.plot(subplots=True, height=120).cols(1)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Controlling layout and targeting multiple axes\n",
    "\n",
    "In Pandas, the layout of subplots can be specified by the `layout` keyword. It can accept `(rows, columns)`. The layout keyword can be used in `hist` and `boxplot` also. If the input is invalid, a `ValueError` will be raised."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df.plot(subplots=True, layout=(2, 3), figsize=(6, 6), sharex=False, backend='matplotlib');"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    ":::{note}\n",
    "In hvPlot, `layout` is not supported, use .`cols(N)` instead.\n",
    ":::"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df.plot(subplots=True, sharex=False, width=160).cols(3)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    ":::{note}\n",
    "In hvPlot, setting `sharex` and/or `sharey` to `True` is equivalent to setting `shared_axes=True`.\n",
    ":::"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Plotting with error bars\n",
    "\n",
    "In Pandas, plotting with error bars is supported in `DataFrame.plot()` and `Series.plot()`. Horizontal and vertical error bars can be supplied to the `xerr` and `yerr` keyword arguments to `plot()`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df3 = pd.DataFrame({'data1': [3, 2, 4, 3, 2, 4, 3, 2],\n",
    "                    'data2': [6, 5, 7, 5, 4, 5, 6, 5]})\n",
    "mean_std = pd.DataFrame({'mean': df3.mean(), 'std': df3.std()})\n",
    "mean_std.plot.bar(y='mean', yerr='std', capsize=4, rot=0, backend='matplotlib');"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    ":::{note}\n",
    "In hvPlot, plotting with error bars is **not** directly supported in `DataFrame.plot()` and `Series.plot()`. Instead, users should fall back to using the `hvplot` namespace directly and the `errorbars()` plotting method it offers.\n",
    ":::"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import hvplot.pandas  # noqa\n",
    "\n",
    "(\n",
    "    mean_std.plot.bar(y='mean', alpha=0.7)\n",
    "    * mean_std.hvplot.errorbars(x='index', y='mean', yerr1='std')\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Plotting tables\n",
    "\n",
    "In Pandas, plotting with matplotlib table is now supported in `DataFrame.plot()` and `Series.plot()` with a `table` keyword. The `table` keyword can accept `bool`, `DataFrame` or `Series`. The simple way to draw a table is to specify `table=True`. Data will be transposed to meet matplotlib’s default layout."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import matplotlib.pyplot as plt\n",
    "\n",
    "fig, ax = plt.subplots(1, 1, figsize=(7, 6.5))\n",
    "df = pd.DataFrame(np.random.rand(5, 3), columns=[\"a\", \"b\", \"c\"])\n",
    "ax.xaxis.tick_top()  # Display x-axis ticks on top.\n",
    "df.plot(table=True, ax=ax, backend='matplotlib');"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    ":::{note}\n",
    "In hvPlot, this same effect can be accomplished by using `hvplot.table()` and creating a layout of the resulting plots with the `+` operator, arranged with `.cols(1)`.\n",
    ":::"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import hvplot.pandas  # noqa\n",
    "\n",
    "(\n",
    "    df.plot(xaxis=False, legend='top_right')\n",
    "    + df.T.hvplot.table()\n",
    ").cols(1)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    ":::{seealso}\n",
    "See Panel's [Tabulator](https://panel.holoviz.org/reference/widgets/Tabulator.html) widget for a more powerful table component.\n",
    ":::"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Colormaps\n",
    "\n",
    "A potential issue when plotting a large number of columns is that it can be difficult to distinguish some series due to repetition in the default colors. To remedy this, `DataFrame` plotting supports the use of the `colormap` argument, which accepts either a colormap or a string that is a name of a colormap.\n",
    "\n",
    "To use the cubehelix colormap, we can pass `colormap='cubehelix'`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df = pd.DataFrame(np.random.randn(1000, 10), index=ts.index)\n",
    "df = df.cumsum()\n",
    "df.plot(colormap='cubehelix', backend='matplotlib');"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    ":::{note}\n",
    "`colormap` is not yet supported in hvPlot for *line* plots. However, in this case `color` can be used instead when set to a HoloViews `Palette` object, that can sample a given colormap at a regularly spaced interval (in this case, the number of overlaid lines).\n",
    ":::"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import matplotlib\n",
    "import holoviews as hv\n",
    "\n",
    "# cubehelix palette only added when the matplotlib extension is loaded\n",
    "hv.Palette.colormaps['cubehelix'] = matplotlib.colormaps['cubehelix']\n",
    "\n",
    "df.plot(color=hv.Palette(\"cubehelix\"))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Colormaps can also be used with other plot types, like bar charts:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "dd = pd.DataFrame(np.random.randn(10, 10)).map(abs)\n",
    "dd = dd.cumsum()\n",
    "dd.plot.bar(colormap='Greens', backend='matplotlib');"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "dd.plot.bar(colormap='Greens')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Parallel coordinates charts:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import hvsampledata\n",
    "from pandas.plotting import parallel_coordinates\n",
    "\n",
    "dfp = hvsampledata.penguins('pandas')[['species', 'bill_length_mm', 'bill_depth_mm', 'flipper_length_mm']].sample(n=50)\n",
    "\n",
    "parallel_coordinates(dfp, 'species', colormap='gist_rainbow');"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from hvplot import parallel_coordinates\n",
    "\n",
    "parallel_coordinates(dfp, 'species', colormap='gist_rainbow')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Andrews curves charts:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from pandas.plotting import andrews_curves\n",
    "\n",
    "andrews_curves(dfp, 'species', colormap='winter')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from hvplot.plotting import andrews_curves\n",
    "\n",
    "andrews_curves(dfp, 'species', colormap='winter')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Plotting directly with HoloViews\n",
    "\n",
    "In some situations it may still be preferable or necessary to prepare plots directly with hvPlot or HoloViews, for instance when a certain type of plot or customization is not (yet) supported by pandas. `Series` and `DataFrame` objects behave like arrays and can therefore be passed directly to HoloViews functions without explicit casts."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import holoviews as hv\n",
    "\n",
    "price = pd.Series(np.random.randn(150).cumsum(),\n",
    "                  index=pd.date_range('2000-1-1', periods=150, freq='B'), name='price')\n",
    "ma = price.rolling(20).mean()\n",
    "mstd = price.rolling(20).std()\n",
    "\n",
    "price.plot(c='k') * ma.plot(c='b', label='mean') * \\\n",
    "hv.Area((mstd.index, ma - 2 * mstd, ma + 2 * mstd),\n",
    "        vdims=['y', 'y2']).opts(color='b', alpha=0.2)"
   ]
  }
 ],
 "metadata": {
  "language_info": {
   "name": "python",
   "pygments_lexer": "ipython3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
